feat(inspector): Phase 1 Capsule Inspector — read-only object-centered view#6
Open
SashaMIT wants to merge 31 commits into
Open
feat(inspector): Phase 1 Capsule Inspector — read-only object-centered view#6SashaMIT wants to merge 31 commits into
SashaMIT wants to merge 31 commits into
Conversation
…d view Borrow Self's mirrors/Morphic UX, re-secured with ElastOS's zero-ambient- authority model. Adds a read-only, permissioned introspection surface that makes the existing security guarantees visible: one screen per capsule showing identity/DID, manifest, affordances, required vs. granted (and denied) capabilities, storage namespaces, Carrier endpoints, provenance, audit log, and running processes. This Phase-1 starter is fully additive — no changes to the trusted core: - docs/CAPSULE_INSPECTOR.md: why/how/what, security invariants, phasing, and the read-only elastos://inspect/* wire contract (backed by existing CapsuleManager + CapabilityManager + AuditLog; no new state). - capsules/capsule-inspector: WASM app capsule (UI) matching the browser capsule conventions. Requires only elastos://inspect/read. Renders live data via the runtime bridge when present; falls back to sample data otherwise so the surface renders standalone for review. Next (not in this commit): the runtime-side read-only handler backing elastos://inspect/*, then Phase 2 (invoke/revoke from the UI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…er scope Audit of the Phase-1 starter found a critical design hole: the runtime's full capsule inventory, capability grants, and audit log are shell-only, but the Inspector requested a flat `inspect/read` and the contract returned all capsules. Backing that literally would let any app enumerate every other capsule's powers and history — the opposite of this feature's purpose. Fix lands the security-critical authorization decision in the trusted core as a pure, unit-tested unit, ahead of the async handler wiring: - elastos-runtime::inspect — InspectScope + authorize_view. Two tiers: `elastos://inspect/all` (System, shell/System only) vs `inspect/read` (SelfOnly). Fails closed: no capability and not shell => denied. 10 unit tests cover the escalation guard, fail-closed, and grant precedence. - capsule.json: full-view product surface now requests `inspect/all` (System-granted), not a flat read. - docs: real Security model section (threat, two tiers, fail-closed, least privilege) + scope/denial semantics in the wire contract + suggested least-invasive integration point (existing ResourceRequest path). - UI: scope badge (system vs self-only) so the view is honest about what it shows; fmtTime hardened against non-numeric input. Verified: `cargo test -p elastos-runtime inspect::` — 10 passed; full crate compiles clean. UI passes `node --check`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…ce tests
Completes Phase 1 end-to-end. Backs the inspect surface in the trusted core,
served on the existing ResourceRequest path (no new protocol variant):
- RequestHandler::handle_inspect — dispatches inspect/capsules (System list),
inspect/capsule{id} (System detail), inspect/self (self detail). Scope is
shell => System, else derived from the validated capability token's grant
pattern via inspect::InspectScope. Out-of-scope detail reads are denied and
audited (AuditEvent::Custom "inspect.out_of_scope").
- build_capsule_view projects CapsuleManager + AuditLog::recent_events into the
documented contract (identity, manifest, affordances, required + audit-derived
granted capabilities, storage, carrier, provenance, audit, processes). Read
only; unknown fields are null rather than fabricated.
- inspect::scope_for_grant aligns the two tiers to real capability patterns:
elastos://inspect/* (System) vs elastos://inspect/self (SelfOnly). The
capability layer enforces the boundary first (a self grant cannot match a
system URI); authorize_view is defense in depth.
- 4 handler conformance tests: shell lists with system scope; non-shell w/o
token denied; self-only token cannot reach system endpoints (the escalation
guard); unknown endpoint -> not_found. Plus 11 inspect unit tests.
- UI bridge call + manifest + docs aligned to the inspect/* / inspect/self
endpoints and grant patterns.
Verified: cargo test -p elastos-runtime inspect — 15 passed; lib compiles
clean. UI passes node --check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…cap reality Closes the test gap on build_capsule_view — a security-relevant projection that previously had no output coverage. Grounded in PRINCIPLES (#16, #12, #7) and the bearer-token capability model confirmed in the capability store. - inspect_detail_renders_contract_without_leaking_authority: launches a real capsule and asserts the nine-field contract is faithful (affordances, required capabilities, storage namespaces, signature_present) AND that the raw manifest signature and any bearer "token" never appear in the output (Principle #16: UI surfaces must not expose bearer tokens or mutation handles — now enforced by test, not prose). - inspect_self_returns_callers_own_record: any capsule (human-driven or agent) can introspect itself via inspect/self with a minimal self grant (Principle #7: humans and agents share one authority model). - Test helper now exposes the capsule manager so tests can launch + inspect. - docs: explain why granted_capabilities is observed-from-audit — ElastOS capabilities are bearer-token object-capabilities with no central per-capsule grant registry, so the audit log is the authoritative safe-to-display source (Principle #12: docs/code/tests agree). Verified: cargo test -p elastos-runtime --lib — 274 passed; 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
The glass box can now act, in the fail-safe direction: revoke a capability, which only ever reduces authority. Grounded in PRINCIPLES (#3, #16, #11) and the bearer-token model. - New endpoint elastos://inspect/revoke (mutation). Requires a Write inspect capability at System scope (or shell); revokes a token by id via CapabilityManager::revoke and emits an inspect.revoke audit event. - Read vs write separation is enforced by the capability *action* dimension, not just the resource: handle_inspect now selects required_action = Write for revoke, Read otherwise. A read-only inspect grant can never drive a mutation. - Self-only scope is rejected for revoke (defense in depth on top of the action gate). - 4 conformance tests: read-only token cannot revoke (the crux — and the victim stays valid); shell can revoke (victim then fails validation); non-shell Write+System operator can revoke; malformed id -> invalid_token_id. - docs: revoke endpoint + read/write tier in the security model and contract. - UI: inspectRevoke bridge stub, intentionally not wired to the token-free read view (Principle #16) — a dedicated System admin surface supplies the id. Verified: cargo test -p elastos-runtime --lib — 278 passed; 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…re doc Addresses a Carrier/Principles alignment review (#4, #9, #7, #12, #10). - UI bridge rewritten as a Carrier-shaped host adapter: one inspectInvoke(operation, payload) targeting the runtime's node-local control API (POST /api/provider/inspect/<op> + x-elastos-home-token), exactly as the library/browser capsules call providers. HTTP is the transport adapter BELOW the capsule contract (CARRIER.md "Where HTTP Fits" #1), not something the capsule "knows"; swapping transport needs no UI change. Degrades to sample data when no token/bridge is present. - docs: new "Transports & Carrier alignment" section recording the two transports (capsule carrier_invoke path — implemented/tested; browser control-API path — not yet wired) feeding ONE authority decision (crate::inspect), and the honest finding that the gateway dispatches via ProviderRegistry::send_raw with GatewayState holding only the registry — so the browser path needs inspect exposed as a registry provider, which also converges on one canonical path (#10) and lets handle_inspect retire. No runtime behaviour change; capsule/carrier path unchanged (278 lib tests still green). UI passes node --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…eManager Tracing the real wiring before building disproved the recommended plan. There is no single capsule world: elastos-runtime::CapsuleManager (rich, reached by RequestHandler::handle_inspect), elastos-server::Runtime/RunningCapsuleInfo (thin), and the gateway (only Arc<ProviderRegistry>). elastos-server has zero CapsuleManager references; the gateway is started with only the registry. Therefore: a supervisor-registered provider cannot hold the managers; the handle_inspect intercept must not be retired (it is the only rich path); and "one canonical path" is a cross-process bridging effort, not a provider registration. Doc updated with evidence and three owner-decision options (registry view now / keep capsule-agent path / bridge gateway->runtime). No code changed; the tested capsule/agent inspect path is untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…gistry Builds the inspect surface where the PC2 product actually runs. Both product transports (capsule carrier_bridge and the browser gateway) dispatch through ProviderRegistry::send_raw, so one `inspect` scheme provider serves both — the real one-canonical-path convergence (#10). - elastos-server::inspect_provider: Provider impl (scheme "inspect") with ops capsules (System list) and capsule (System detail). Rich nine-field projection from the manifest; reuses elastos_runtime::inspect::InspectScope. Data read via an InspectSource trait (impl for runtime::Runtime), decoupling the provider from the server's capsule tracking. 4 unit tests incl. the #16 no-leak guarantee (raw signature / bearer token never echoed). - runtime::RunningCapsuleInfo retains the launch manifest (one construction site updated) so the rich projection has real data. - serve_cmd registers the provider on the shared registry (the one used by the carrier bridge AND handed to the supervisor/gateway) on the single-VM serve path, where running_capsules is populated — so it works end-to-end there. Honest gap (documented): the multi-capsule/browser path does not populate runtime::running_capsules, so inspect returns [] there until the server's capsule tracking is unified into InspectSource. Projection/scope/no-leak/ transport wiring are done; data-source unification is the next step. Verified: cargo test -p elastos-server inspect_provider — 4 passed; full crate compiles clean (730 other tests unaffected by the RunningCapsuleInfo change). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…vices Composable InspectSource so the main/browser product path shows real capsules, not an empty list. - RuntimeInspectSource: the server Runtime's running-capsule registry (rich, manifest-backed; populated on the single-VM serve path). - RegistryInspectSource: registered provider schemes from ProviderRegistry (the running provider services; always populated on the main path). Thin — id = provider:<scheme>, manifest None. - AggregateInspectSource: unions sources, de-dups by id — the unification. - Provider now holds a strong Arc<dyn InspectSource>; each source holds a Weak reference to its backing object, so registering on the registry never forms a reference cycle. - serve_cmd: single-VM path wires RuntimeInspectSource; main path wires Aggregate[Runtime, Registry] on the shared registry the supervisor/gateway use — so the browser Inspector lists running services end-to-end. Honest remaining enrichment (documented): provider-scheme entries are thin (registry carries no per-provider manifest; schemes() omits sub-providers). Full nine-field provider detail needs a catalog source reading capsule.json. Verified: cargo test -p elastos-server inspect_provider — 6 passed (incl. registry + aggregate + no-leak); lib + binary compile clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Promotes the browser/product inspect from thin provider-scheme entries to the full nine-field view by reading installed capsule manifests from disk. - CatalogInspectSource reads <data_dir>/capsules/<name>/capsule.json, projects the full manifest (capabilities, affordances, provenance), and marks each capsule running when the scheme it `provides` is registered live, else installed. id = capsule:<name>; path-traversal ids rejected. - serve_cmd main path now aggregates [RuntimeInspectSource, CatalogInspectSource] on the shared registry, so the browser Inspector shows installed capsules with real manifests + running status. - RegistryInspectSource kept as an available source (built-in schemes) but no longer the default; catalog supersedes it with rich data. Tests (+2, 8 total): catalog reads a manifest richly and never leaks the raw signature (#16); marks running when the provided scheme is registered; rejects path-traversal ids. Verified: cargo test -p elastos-server inspect_provider — 8 passed; lib + binary compile clean. Honest remaining: schemes() omits sub-providers (running-status may miss them); live audit/grant aggregation into the projection still pending. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Both remaining enrichments. Sub-provider coverage (#1): - ProviderRegistry::sub_provider_schemes() (elastos-runtime, additive) lists the elastos:// sub-provider schemes (did/key/peer/…) that schemes() omits. - RegistryInspectSource lists main + sub schemes; CatalogInspectSource's running-status detection now matches sub-provider-backed schemes too. Live per-capsule audit (#2): - AuditSource trait + AuthAuditSource: reads the signed runtime audit log (RuntimeAuditEventV1 in auth state), correlates by capsule_id (capsule name), and fills the detail view's audit section — recent events (newest-first, capped) + total/denied counts. Runs on a blocking task off the async workers. - InspectProvider gains an optional audit source (with_audit builder); project renders the live audit. Records expose only safe fields (ts/event/reason/ success) — no signatures or handles (#16). - serve_cmd wires AuthAuditSource(data_dir) on both serve paths. Honest remaining: granted_capabilities stays empty — bearer-token caps have no central registry and RuntimeAuditEventV1 has no resource/action, so observed grants need a capability-event source that records them. Verified: cargo test -p elastos-server inspect_provider — 10 passed (incl. live audit + sub-provider tests, #16 no-leak preserved); cargo test -p elastos-runtime --lib — 278 passed (additive registry method, no regression). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…ection) Per Rong Chen's emphasis that Self missed metadata-driven reflection: the manifest's affordances already carry typed input_schema/output_schema, but the projection dropped them. Surface them so an affordance is a machine-readable interface contract (risk/approval/audit + input/output schema), not just a display label — the basis for typed, location-agnostic, capability-gated invocation over Carrier (the modern CAR direction). - inspect_provider: affordance projection includes input_schema/output_schema. - test asserts the typed schema is surfaced; #16 no-leak preserved. - docs: lineage note — capsule granularity (not objects), OS-level reflection, metadata-driven contract; CAR connection; metadata-driven invocation flagged as a deeper direction to plan, not assume. Verified: cargo test -p elastos-server inspect_provider — 10 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Prototype of the reflective kernel Rong calls "metadata-driven reflection" (CAR): the bridge from inspect (read the typed contract) to invoke (act on it). - elastos-runtime::invoke — pure, transport-agnostic planner. Given an affordance's typed metadata it (1) validates call args against input_schema (minimal type + required-field check) and (2) derives the policy gate: capability Action from risk class, plus approval + audit modes. Mirrors crate::inspect as a decision core; dispatch/marshalling/Carrier transport are intentionally out of scope (architecture to be planned with Rong). 6 tests. - docs/INSPECTOR_BRIEF.md — one-page, shareable status + direction: the synthesis (Self UX x CAR metadata x capability/Carrier security), how it fixes Self's three gaps, what is built/tested today, the prototype, the inspect->invoke->location-agnostic->cross-language roadmap (for his input), honest gaps, and the time/commercialization framing. Verified: cargo test -p elastos-runtime invoke:: — 6 passed; 278 others unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…erification
Live verification found a real gap registration had hidden: the browser path
404'd. gateway_provider_proxy's allowed_apps match had no `inspect` case, so
POST /api/provider/inspect/<op> fell through to "Gateway provider not found"
before reaching the registry. Fixed + verified end-to-end.
- gateway_provider_proxy: add an `inspect` scheme arm — read ops (capsules,
capsule) allowed for the System operator app (SYSTEM_CAPSULE_ID); write ops
(revoke) intentionally not exposed through the browser proxy.
- gateway_tests/inspect.rs (NEW): HTTP-level tests through the real
gateway_router — no token => 403; System token => 200 listing the installed
capsule (full leg: token validate -> allow-list -> registry -> provider);
a non-System (Library) token is rejected.
- inspect_provider: registry_dispatch_reaches_inspect_provider test proving the
ProviderRegistry::send_raw("inspect", ..) leg both transports converge on.
Security review (separate pass): clean — no HIGH/MEDIUM findings; path
traversal, scope/authz, #16 no-leak, and read/write revoke separation all
sound.
Verified: cargo test -p elastos-server inspect_provider (11) + inspect_capsules
(2) — all green; compiles clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…→50%) Milestone push: inspect complete on both transports, with content provenance, and the glass box can now *preview* action. 1. Carrier-leg consistency: build_capability_resource now maps `inspect` to a concrete elastos://inspect/<op> resource (was the malformed `inspect://*` default), so the capsule/carrier transport gates inspect identically to the gateway/handler path. + unit test. 2. Metadata-driven invoke PREVIEW: new read-only `inspect/plan` op exposes the elastos-runtime::invoke planner — validates args against the affordance's input_schema and returns the capability/approval/audit gate the call would require. No effect dispatched (that architecture is Rong's to shape). + 3 tests (valid gate, type-mismatch, unknown affordance). 3. Provenance enrichment: CatalogInspectSource attaches the content CID from <data_dir>/components.json; project() surfaces it in identity + provenance (Principle #15). + test. Verified: cargo test -p elastos-server inspect — 18 passed (provider 15 + naming 1 + gateway HTTP 2); compiles clean. Honest remaining: live granted_capabilities (needs capability-use events with resource+action), real invoke dispatch, location-agnostic over Carrier, cross-language — all Rong-call / Tier 3+. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…ty (→65%)
Both product transports now proven end-to-end, and the read-only invoke
preview is reachable from the browser.
- carrier_bridge test: a capability-gated carrier_invoke("elastos://inspect/
capsules") validates the inspect capability and reaches the provider;
rejected without a token. The capsule/agent transport is now verified e2e,
matching the gateway/browser leg — both converge on the same provider via the
Carrier-shaped capability call.
- gateway allow-list: add `plan` to the inspect read ops (System scope) so the
browser Inspector can use the metadata-driven invocation preview. plan is
read-only and returns only a gate descriptor (no token/handle, #16).
- docs: both transports marked verified; read ops (capsules/capsule/plan) on
both, write (revoke) runtime-only.
Security/principles check (this push): #16 holds (plan returns requirements not
grants); hostile `op` cannot escape the inspect/* grant (ResourceId::matches
rejects ".."); provenance CID read from a fixed path. Carrier framework: the
capsule contract stays Carrier-shaped; HTTP/stdio are adapters below it.
Verified: cargo test -p elastos-server inspect (19) + the carrier e2e test — all
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Cross-branch analysis vs feat/ddrm-hardening-and-creator-parity showed the Inspector is the governance/visibility layer over DDRM's providers — but it was under-representing them: DDRM providers (key/decrypt/rights/encrypt/publish/ chain) express their powers via manifest `authority.capabilities[].operations`, not `interfaces[].methods`, which our projection dropped. - inspect_provider project(): surface declarative provider `authority` (reason + capabilities[resource/actions/operations] + audit_events). Now the Inspector shows what a provider can actually do (e.g. key release, decrypt render, rights decisions). Allow-listed, declarative-only — no secrets/handles (#16); + test (key-provider authority surfaced, signature still not leaked). - docs/INSPECT_DDRM_MERGE_NOTES.md: integration plan. No model divergence; merge order (DDRM first, inspect on top); conflict matrix (3 LOW, 1 HIGH); the HIGH is a SILENT semantic break — DDRM's required_action_for(op) omits inspect ops → defaults Admin → fail-closes our Read carrier path; required reconciliation documented. Coordination gap: v0.5/Anders line is not on the remote. Security: declarative authority metadata only; #16 preserved (tested). No DDRM file overlap in this change (project() is ours). Verified: cargo test -p elastos-server inspect — 20 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Frontend-only (no Rust build, no DDRM file overlap). The Inspector UI now shows a "Provider authority (powers)" card — reason + each capability's resource/actions/operations + audit events — so an operator visually sees what a provider capsule can actually do (e.g. wallet sign, and DDRM's key release / decrypt render / rights decisions once those manifests are present). Makes the agent-safe-governance wedge demoable. Sample wallet-provider enriched with authority so it renders standalone. Verified: node --check inspector.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Mirror the provider-authority projection in the embedded-runtime path (RequestHandler::build_capsule_view) so both projections — product (inspect_provider) and embedded/agent (handle_inspect) — surface provider `authority` consistently (Principle #12: docs/code/tests agree). Same allow-listed, declarative-only shape (reason + capabilities + audit_events); #16 preserved. DDRM does not touch request_handler.rs — no conflict. Verified: cargo test -p elastos-runtime inspect — 21 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
A real merge dry-run (isolated worktree, nothing pushed) of feat/capsule-inspector onto the DDRM tip confirmed GO-WITH-NOTES and corrected the reconciliation list: - The merge auto-merges with ZERO conflict markers — including carrier_bridge.rs (the HIGH one). So the reconciliation is mandatory AND invisible to git. - The required_action_for inspect-ops fix is empirically proven: omitting it fails the carrier inspect test (capability_denied); restoring it passes. - NEW finding the static analysis missed: DDRM added an `audit_log` field to the shared GatewayState struct, so our gateway_tests/inspect.rs literal must add `audit_log: Arc::new(OnceLock::new())` or the lib-test target won't compile. - After both fixes: build OK; 20 inspect tests + carrier e2e + runtime tests pass. - registry.rs / carrier validate block / use-lines need no manual merge. Merge notes updated with the validated 2-step recipe. Our branch is unchanged by the dry-run (separate worktree). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Extend inspect/plan with a second reflective mode. Where the existing mode previews an interfaces[].methods affordance, the new "operation" mode reflects a provider capsule's authority.capabilities[] — how DDRM providers (key release, decrypt render, rights, chain broadcast) actually declare their powers — and returns the exact capability tuple a call would demand: the resource, the full set of actions a caller's capability must cover, and the audit events emitted. Dispatches nothing; derived entirely from the capsule's own metadata, so a preview can never under-state the gate the runtime later enforces. This is the agent-safe-computing wedge made tangible: "before I let this run, show me precisely what authority it asks for." - elastos-runtime::invoke: plan_provider_operation() + ProviderOperationPlan; parse_action() maps declared action strings fail-closed (unknown keyword is an error, never a silent drop that would weaken the gate). 4 unit tests. - elastos-common: re-export ProviderAuthority / ProviderCapabilitySchema. - inspect_provider: handle_plan now routes interface+method vs operation (never mixed; neither -> invalid_request, fail-closed). 3 handler tests. - inspector UI: each declared operation gets a "preview gate" button rendering the tuple inline; offline twin (localPlanOperation) mirrors the server contract so the demo works with sample data. - docs: CAPSULE_INSPECTOR.md plan wire-contract documents both modes. Gating unchanged: still the System-scoped, read-only "plan" op (no new op, no bypass). build OK; invoke 10 + inspect 23 + 751 server lib tests; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Two parallel-safe hardening increments (no DDRM dependency). 1. Merge tripwire for the inspect op->action contract. provider_resource::inspect_op_required_action is now the single source of truth (reads = Read; revoke = Write; unknown = None, fail-closed). The carrier test carrier_inspect_ops_match_canonical_action_contract drives a real carrier_invoke per inspect op with a token minted at that action and asserts it clears the capability gate. Today our gate validates token.action() so it passes by construction; when DDRM lands and the gate becomes validate(.., required_action_for(op), ..), any inspect op the DDRM map omits fail-closes to Action::Admin and this test goes RED at merge - instead of breaking silently through git's clean auto-merge. Converts the documented reconciliation note into an enforced invariant. 2. Provenance derived honestly, never fabricated (#15, #11, #16). project() now computes a fail-closed trust_level (signed -> content-addressed -> unsigned), a non-secret 16-hex signature_fingerprint (SHA-256 over the decoded signature - identifies which signature without echoing it), the self-declared author, and a did ONLY when one genuinely exists (capsule id or did: author). A verified signer (signed_by) stays null: the manifest schema carries no signer DID/pubkey, so the author is never presented as verified. UI provenance card + docs updated. Tests: invoke 10, inspect 29 (+6), provider_resource contract pinned, carrier tripwire green; 757 server lib tests pass. New code clippy-clean. (Three pre-existing clippy nits remain in request_handler.rs / gateway_capsule_catalog.rs, outside this change surface.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…-data fidelity The signed runtime audit log carries signer_did + signature per event, but the Inspector wasn't surfacing them. Add attestation to the audit projection: each recent record now reports `signed` (a signature is present) and `signer` (the attesting DID), and counts include `attested`. This is the verified-signer evidence the audit plane actually holds (#15) — surfaced as presence + DID, with the signature material never echoed (#16). UI audit card shows the attested count and a per-event "⛓ attested" pill (signer DID on hover); sample data enriched so the offline demo shows it too. granted_capabilities remains honestly empty: RuntimeAuditEventV1 carries no resource/action, so an observed grant list cannot be derived without fabrication. Documented, not invented. Tests: inspect 29 (attestation asserted end-to-end); 757 server lib tests pass; new code clippy-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…ege test Two security hardening items, all on owned code (no DDRM-risky files). 1. Provider-op preview can no longer under-state the gate. plan_provider_operation previously returned the FIRST capability block declaring an operation. A manifest that splits one operation across several blocks (e.g. release under both elastos://key/* read AND elastos://decrypt/* execute+admin) could thus hide authority the call also requires. It now aggregates EVERY matching block and returns the union of resources + actions, deduped and order-preserving, and still fails closed if ANY matching block declares an unknown action. Wire contract: data.resource (string) -> data.resources (array). UI + offline twin + docs updated to match. +2 invoke tests (split-block union; fail-closed across blocks). 2. Edge least-privilege test (#16): assert the write op `revoke` returns 404 via the browser gateway even WITH a System token — mutation stays on the capability-gated carrier/admin path, never the browser proxy. Tests: invoke 12, inspect 30, 758 server lib tests; new code clippy-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Replace the [..16] slice on the digest hex with .chars().take(16): cannot panic regardless of the digest's hex length, removing a latent assumption that the hasher always yields >=16 hex chars. No behavior change for SHA-256. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
A setup + manual-test guide for feat/capsule-inspector: build/test/lint commands, the two test paths (fast sample mode; live serve + System token), the manual UI checklist, and copy-paste curl security checks (no token -> 403, System -> 200, revoke -> 404, no secret leakage). Documents what is intentionally not built yet (approval loop, dispatch, empty granted_capabilities) so testers don't flag them as bugs, and flags the live System-token / UI-route step as a known frontier with a sample-mode fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
The inspect feature was committed without running the project's real gate (`just lint`/`just verify`): `cargo fmt --check` failed on all 7 of our branch files, and `cargo clippy --workspace --all-targets -- -D warnings` flagged two needless borrows in our code. Bring our branch's own contribution up to the gate: - rustfmt the 7 inspect files (scoped to our files only; no `cargo fmt --all`). - clippy: `to_value(info.trust_level)` (TrustLevel is Copy) and `ResourceId::new(format!(...))` in the carrier tripwire test. No logic changes. Verified: `cargo fmt --check` passes; our crates are clippy-clean under `-D warnings`; `cargo test --workspace` is green. The only remaining clippy errors are pre-existing on main (gateway_capsule_catalog.rs field_reassign_with_default), not our work — left untouched per shared-tree discipline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…ignore]d tests Apply the LESSONS.md flywheel pattern: turn the inspector's honest gaps into a build-visible, self-closing registry instead of prose that rots. - docs/KNOWN_GAPS.md: registry table of the 4 open gaps (granted_capabilities, verified signer/signed_by, invoke dispatch, human-approval loop) with why-open + ratchet + close criteria, plus an "enforced invariants" section (merge tripwire, no-leak, fail-closed scope) so safe-by-construction items aren't mistaken for open work. - Two #[ignore]d ratchet tests (G1 granted_capabilities, G2 verified signer): they encode the desired end-state and FAIL today, so they are non-blocking in a shared tree (skipped by default) yet flip to green the moment the gap closes (delete the #[ignore]). Verified: default run skips them (30 passed, 2 ignored); `--ignored` run shows both failing — proving they're real ratchets, not vacuous passes. - G3/G4 are registry rows only — no compiling test exists until the feature scaffold does; a fabricated one would be vacuous (default to "not a finding"). fmt --check PASS; new code clippy-clean (pre-existing gateway_capsule_catalog #4 untouched, not ours). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
Record the direction this branch is a foundation for, so the intent behind the substrate isn't lost. Frames the work as completing one control loop — reflect → preview → approve → act → audit — then putting selectable shells (including an intent-led AI shell with a contained agent capsule) on top. Contents: where we are (the built substrate); ordered roadmap (approval loop next; dispatch merge-gated on DDRM; shell-manager + selectable shells; intent-led AI shell; pluggable local/cloud intelligence; a Morphic/Godot living-object canvas — presentation only, core stays the authority); the experience we're building toward (authority made legible: trust as material, gates as visible circuits, approval as a deliberate ceremony, audit as a timeline); business model (shell tiers, DRM-self-enforced access, agent-safe enterprise wedge); and the trust/security framing (build-time vs run-time boundaries; open code != open authority; the real risks are the signing trust root, automation bias, and TCB creep — not forking). Direction, not a commitment. Honest real-vs-vision split; gaps stay tracked via the KNOWN_GAPS ratchet pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
…review The "approve" step of the control loop (reflect → preview → APPROVE → act), parallel-safe and read-only. - elastos-runtime::approval (new, pure): `decide(mode, approver)` is fail-closed — the only path to Approved without an explicit yes is an affordance declared as needing no approval; User/RuntimePolicy default to PendingApproval; an explicit no always wins. `required_approval(actions)` scales the requirement with action strength (anything beyond read/message needs a human). 3 tests. - inspect/intent (new provider op, read-only): given a capsule + operation, derives the gate (via plan), the approval it requires, and the fail-closed default decision. Records nothing, dispatches nothing. - Gated consistently: `intent` added to the canonical op→action contract (Read) and the System-only browser allow-list. - Decisions: `revoke` and recorded approve/deny stay on the runtime/dispatch (mutation) path — the product InspectProvider remains a read-only projection. Recording pairs with dispatch (merge-gated). fmt --check PASS; targeted tests green (approval 3, inspect incl. intent 31 +2 ratchets ignored, provider_resource contract 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
- CAPSULE_INSPECTOR.md: add the inspect/intent wire contract (approval-intent preview); add a "path note" clarifying revoke + self are served on the embedded RequestHandler (shell) path while the product InspectProvider is a read-only projection (capsules/capsule/plan/intent) — closes the contract-honesty gap. - KNOWN_GAPS.md: G4 decision core DONE (approval + intent, fail-closed, tested); remaining = recording a signed approve/deny, which pairs with dispatch (G3). (An orchestrator CLAUDE.md was written locally but is .gitignored by repo policy, so it stays a local contract and is not committed.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Borrow Self's mirrors/Morphic UX, re-secured with ElastOS's zero-ambient-
authority model. Adds a read-only, permissioned introspection surface that
makes the existing security guarantees visible: one screen per capsule showing
identity/DID, manifest, affordances, required vs. granted (and denied)
capabilities, storage namespaces, Carrier endpoints, provenance, audit log,
and running processes.
This Phase-1 starter is fully additive — no changes to the trusted core:
the read-only elastos://inspect/* wire contract (backed by existing
CapsuleManager + CapabilityManager + AuditLog; no new state).
capsule conventions. Requires only elastos://inspect/read. Renders live data
via the runtime bridge when present; falls back to sample data otherwise so
the surface renders standalone for review.
Next (not in this commit): the runtime-side read-only handler backing
elastos://inspect/*, then Phase 2 (invoke/revoke from the UI).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_016ZKy5Cca9RzwDuLb1szdeq